//                 _              
//                | |             
//   __ _ _ __ ___| |__ __  _____ 
//  / _` | '__/ __| '_ \\ \/ / __|
// | (_| | | | (__| | | |>  <\__ \
//  \__,_|_|  \___|_| |_/_/\_\___/
//                                
// Copyright 2010 Scott Dixon http://www.archxs.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.archxs.commons.collections;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.archxs.commons.Variant;

/**
 * {@link VariantProperties} implementation that is a proxy for a "backing"
 * <code>HashMap<String,String[]>()</code> instance.
 */
public class VariantPropertiesMapProxy implements VariantProperties {

    /**
     * Creates a new {@link VariantProperties} instance that is backed by a 
     * {@link Map<String,String[]>}.
     * @param  proxiedMap The actual datastore that is proxied by the returned 
     *                    {@link VariantProperties} object.
     * @return {@link VariantProperties} instance that operates on the provided
     *         map instance.
     */
    public static VariantProperties proxyInstance(final Map<String,String[]> proxiedMap) {
        return proxyInstance(null, proxiedMap);
    }

    /**
     * Creates a new {@link VariantProperties} instance that is backed by a 
     * {@link Map<String,String[]>}.
     * @param  variantType The type of Variant to use.
     * @param  proxiedMap  The actual datastore that is proxied by the returned 
     *                     {@link VariantProperties} object.
     * @return {@link VariantProperties} instance that operates on the provided
     *         map instance.
     */
    public static VariantProperties proxyInstance(final Class<? extends Variant> variantType, final Map<String,String[]> proxiedMap) {
        return new VariantPropertiesMapProxy(variantType, proxiedMap);
    }
    
    /**
     * Creates a new {@link VariantProperties} instance that is backed by request
     * parameters from a J2EE Http request.
     * @param  requestParameters  The actual datastore that is proxied by the returned 
     *                            {@link VariantProperties} object.
     * @return {@link VariantProperties} instance that operates on the provided
     *         map instance.
     */
    @SuppressWarnings("unchecked")
    public static VariantProperties proxyJ2EERequestParameters(final Map<?,?> requestParameters) {
        return proxyInstance(null, (Map<String,String[]>)requestParameters);
    }
    
    /**
     * Creates a new {@link VariantProperties} instance that is backed by request
     * parameters from a J2EE Http request.
     * @param  variantType        The type of Variant to use.
     * @param  requestParameters  The actual datastore that is proxied by the returned 
     *                            {@link VariantProperties} object.
     * @return {@link VariantProperties} instance that operates on the provided
     *         map instance.
     */
    @SuppressWarnings("unchecked")
    public static VariantProperties proxyJ2EERequestParameters(final Class<? extends Variant> variantType, final Map<?,?> requestParameters) {
        return proxyInstance(variantType, (Map<String,String[]>)requestParameters);
    }
    
    // +-----------------------------------------------------------------------+
    // | Map
    // +-----------------------------------------------------------------------+
    @Override
    public void clear() {
        m_inner.clear();
    }

    @Override
    public boolean containsKey(final Object key) {
        return m_inner.containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
        if (value instanceof Variant) {
            return VariantCollectionProxy.contains(m_inner.values(), (Variant)value);
        }
        return false;
    }

    @Override
    public Set<Entry<String, Variant> > entrySet() {
        return new EntrySetProxy(m_inner.entrySet());
    }

    @Override
    public Variant get(final Object key) {
        return Variant.valueOf(m_inner.get(key));
    }

    @Override
    public boolean isEmpty() {
        return m_inner.isEmpty();
    }

    @Override
    public Set<String> keySet() {
        return m_inner.keySet();
    }

    @Override
    public Variant put(final String key, final Variant value) {
        if (null == value) {
            return Variant.valueOf(m_inner.put(key, null));
        } else {
            return Variant.valueOf(m_inner.put(key, value.getArray(new String[value.getArrayLength()])));
        }
    }

    @Override
    public void putAll(final Map<? extends String, ? extends Variant> m) {
        for(final Entry<? extends String, ? extends Variant> entry : m.entrySet()) {
            m_inner.put(entry.getKey(), entry.getValue().getArray(ARRAY_CASTER));
        }
    }

    @Override
    public Variant remove(final Object key) {
        return Variant.valueOf(m_inner.remove(key));
    }

    @Override
    public int size() {
        return m_inner.size();
    }

    @Override
    public Collection<Variant> values() {
        return VariantCollectionProxy.proxyInstance(m_variantType, m_inner.values());
    }
    
    // +-----------------------------------------------------------------------+
    // | PRIVATE
    // +-----------------------------------------------------------------------+
    VariantPropertiesMapProxy(final Class<? extends Variant> type, final Map<String,String[]> innerMap) {
        if (null == innerMap) {
            throw new IllegalArgumentException("No map provided to map proxy constructor.");
        }
        if (null == type) {
            m_variantType = Variant.getVariantType();
        } else {
            m_variantType = type;
        }
        m_inner = innerMap;
    }

    /** Typed array used for {@link Variant#getArray(T[])} */
    private final static String[] ARRAY_CASTER = new String[0];
    
    /** Typed array used for {@link Variant#getArray(T[])} */
    @SuppressWarnings("unchecked")
    private final static Entry<String,Variant>[] ENTRY_ARRAY_CASTER = (Entry<String,Variant>[])new Entry<?,?>[0];
    
    /** The proxied collection and authoritative datasource. */
    private final Map<String,String[]> m_inner;
    
    /** The type of variant used by this map. */
    private final Class<? extends Variant> m_variantType;
    
    /** 
     * Wraps an <code>Entry&lt;String,String[]&gt;</code> and presents it as
     * an <code>Entry&lt;String,Variant&gt;</code>. 
     */
    private static class EntryProxy implements Entry<String,Variant> {

        /** Proxied entry data. */
        private final Entry<String,String[]> m_innerEntry;
        
        /** 
         * Create a new EntryProxy.
         * @param  innerEntry The inner entry to proxy.
         */
        EntryProxy(final Entry<String,String[]> innerEntry) {
            m_innerEntry = innerEntry;
        }
        
        // +-------------------------------------------------------------------+
        // | Entry
        // +-------------------------------------------------------------------+
        @Override
        public String getKey() {
            return m_innerEntry.getKey();
        }

        @Override
        public Variant getValue() {
            return Variant.valueOf(m_innerEntry.getValue());
        }

        @Override
        public Variant setValue(final Variant value) {
            String[] valueToSet = null;
            if (null != value) {
                valueToSet = value.getArray(ARRAY_CASTER);
            }
            return Variant.valueOf(m_innerEntry.setValue(valueToSet));
        }
        
    };
    
    /**
     * Proxies a <code>Set</code> of {@link Entry}s contained within our 
     * inner map.
     */
    private class EntrySetProxy implements Set<Entry<String,Variant> > {

        // +-------------------------------------------------------------------+
        // | Set
        // +-------------------------------------------------------------------+
        @Override
        public int size() {
            return m_innerSet.size();
        }

        @Override
        public boolean isEmpty() {
            return m_innerSet.isEmpty();
        }

        @Override
        public boolean contains(final Object o) {
            boolean contains = false;
            if (o instanceof Entry<?,?>) {
                //TODO: no hope of being atomic. Evaluate this.
                @SuppressWarnings("unchecked")
                final Entry<String,Variant> entry = (Entry<String,Variant>)o;
                return (m_inner.containsKey(entry.getKey()) && VariantCollectionProxy.contains(m_inner.values(), entry.getValue()));
            }
            return contains;
        }

        @Override
        public Iterator<Entry<String, Variant>> iterator() {
            final Iterator<Entry<String,String[]> > innerIter = m_innerSet.iterator();
            return new Iterator<Entry<String, Variant> >() {

                @Override
                public boolean hasNext() {
                    return innerIter.hasNext();
                }

                @Override
                public Entry<String, Variant> next() {
                    return new EntryProxy(innerIter.next());
                }

                @Override
                public void remove() {
                    innerIter.remove();
                }
            };
        }

        @Override
        public Object[] toArray() {
            return toArray(ENTRY_ARRAY_CASTER);
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(final T[] a) {
            final Object[] innerArray = m_innerSet.toArray();
            final int arrayLen = innerArray.length;
            final Entry<String, Variant>[] variantArr;
            if (a.length >= innerArray.length) {
                variantArr = (Entry<String,Variant>[])a;
            } else {
                variantArr = (Entry<String, Variant>[]) new Entry<?, ?>[arrayLen];
            }
            for(int i = 0; i < arrayLen; ++i) {
                variantArr[i] = new EntryProxy((Entry<String, String[]>) innerArray[i]);
            }
            
            return (T[])variantArr;
        }

        @Override
        public boolean add(final Entry<String, Variant> e) {
            final String[] newValue = e.getValue().getArray(ARRAY_CASTER);
            final String[] prevValue = m_inner.put(e.getKey(), newValue);
            return Arrays.equals(prevValue, newValue);
        }

        @Override
        public boolean remove(final Object o) {
            if (o instanceof Entry<?,?>) {
                @SuppressWarnings("unchecked")
                final Entry<String,Variant> eo = (Entry<String,Variant>)o;
                return (m_inner.remove(eo.getKey()) != null);
            }
            return false;
        }

        @Override
        public boolean containsAll(final Collection<?> c) {
            if (null != c && c.size() > 0) {
                boolean foundVariant = false;
                for(final Object value : c) {
                    // TODO: this is a high runtime. See if there is a better way.
                    if (!contains(value)) {
                        return false;
                    } else {
                        foundVariant = true;
                    }
                }
                return foundVariant;
            }
            return false;
        }

        @Override
        public boolean addAll(final Collection<? extends Entry<String, Variant>> c) {
            if (null != c) {
                boolean changed = false;
                for(final Entry<String,Variant> entry : c) {
                    final String[] previousValue = m_inner.put(entry.getKey(), entry.getValue().getArray(ARRAY_CASTER));
                    if (null == previousValue || !Variant.valueOf(previousValue).equals(entry.getValue())) {
                        changed = true;
                    }
                }
                return changed;
            }
            return false;
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean retainAll(final Collection<?> c) {
            boolean modified = false;
            if (null != c) {
                final Collection<Entry<String,Variant> > valuesToRetain = (Collection<Entry<String,Variant> >)c;
                // TODO: not a great runtime here.
                final Map<String,Variant> retainMap = new HashMap<String,Variant>();
                for(final Entry<String,Variant> valueToRetain : valuesToRetain) {
                    retainMap.put(valueToRetain.getKey(), valueToRetain.getValue());
                }
                final Iterator<Entry<String,String[]> > i = m_innerSet.iterator();
                while(i.hasNext()) {
                    final Entry<String,String[]> entry = i.next();
                    final Variant value = retainMap.get(entry.getKey());
                    if (null == value || !Arrays.equals(value.getArray(ARRAY_CASTER), entry.getValue())) {
                        i.remove();
                        modified = true;
                    }
                }
            }
            return modified;
        }

        @Override
        public boolean removeAll(final Collection<?> c) {
            boolean modified = false;
            if (null != c) {
                @SuppressWarnings("unchecked")
                final Collection<Entry<String,Variant> > removeList = (Collection<Entry<String,Variant> >)c;
                for(final Entry<String,Variant> removee : removeList ) {
                    if (remove(removee)) {
                        modified = true;
                    }
                }
            }
            return modified;
        }

        @Override
        public void clear() {
            m_innerSet.clear();
        }
        
        // +-------------------------------------------------------------------+
        // | PRIVATE
        // +-------------------------------------------------------------------+
        /**
         * Create a new Set that proxies an entry set taken from our outer object's
         * {@link VariantPropertiesMapProxy#m_inner} collection.
         */
        private EntrySetProxy(final Set<Entry<String,String[]> > innerEntrySet) {
            m_innerSet = innerEntrySet;
        }
        
        /** Entry set being proxied. */
        private final Set<Entry<String,String[]> > m_innerSet;
    };
    
}
